using System;
using System.Diagnostics;
using Dida.Tool;
using System.Collections.Generic;

namespace Dida
{
	public class DynamicState<GlueTrait> : IState<GlueTrait>
		where GlueTrait : StateGlueTrait
	{
		StateDOM<GlueTrait> stateDOM;
		DynamicTraitClass traitClass = new DynamicTraitClass();
		GlueTrait glue = null;
		DynamicStateMachine<GlueTrait> stateMachine;

		internal DynamicState(StateDOM<GlueTrait> stateDOM,
			DynamicStateMachine<GlueTrait> stateMachine)
		{
			this.stateMachine = stateMachine;
			
			this.stateDOM = stateDOM;
			stateDOM.Traits.ItemAdded += OnTraitAdded;
			stateDOM.Traits.ItemRemoved += OnTraitRemoved;
			stateDOM.Traits.ItemNameChanged += OnTraitNameChanged;

			stateDOM.ExitTransitions.ItemAdded += OnExitTransitionAdded;
			stateDOM.ExitTransitions.ItemRemoved += OnExitTransitionRemoved;
			stateDOM.ExitTransitions.ItemNameChanged += OnExitTransitionNameChanged;

			int nTraits = stateDOM.Traits.Count;
			string[] names = new string[nTraits];
			Type[] types = new Type[nTraits];
			int glueIndex = -1;
			for(int i = 0;i < nTraits;i++)
			{
				names[i] = stateDOM.Traits[i].Name;
				types[i] = stateDOM.Traits[i].TraitType;

				if(typeof(GlueTrait).IsAssignableFrom(types[i]))
				{
					glueIndex = i;
				}
			}
			DynamicTrait[] traits = traitClass.AddTraits(names,types);
			glue = (GlueTrait)traits[glueIndex].TraitObject;

			foreach (StateTransitionDOM<GlueTrait> transition in stateDOM.ExitTransitions)
			{
				DynamicTrait curTrait = traitClass.AddTrait("ExitTransition_" + transition.Name, 
					transition.TraitType);
				StateTransition<GlueTrait> transitionTrait = (StateTransition<GlueTrait>)curTrait.TraitObject;
				transitionTrait.Initialize(new TransitionGlue(transition.To, stateMachine));
			}
		}

		public StateTrait<GlueTrait> GetTrait(Type type)
		{
			return (StateTrait<GlueTrait>)traitClass.GetTrait(type);
		}

		public T GetTrait<T>()
			where T : StateTrait<GlueTrait>
		{
			return traitClass.GetTrait<T>();
		}

		private void OnTraitAdded(object sender, ItemAddedArgs args)
		{
			traitClass.AddTrait(args.NewItem.Name, 
				((StateTraitDOM<GlueTrait>)args.NewItem).TraitType);
		}

		private void OnTraitRemoved(object sender, ItemRemovedArgs args)
		{
			traitClass.RemoveTrait(args.RemovedItem.Name);
		}

		private void OnTraitNameChanged(object sender, ItemNameChangedArgs args)
		{
			traitClass.RenameTrait(args.OldName, args.NewName);
		}

		private void OnExitTransitionAdded(object sender,ItemAddedArgs args)
		{
			throw new NotImplementedException();
		}

		private void OnExitTransitionRemoved(object sender,ItemRemovedArgs args)
		{
			throw new NotImplementedException();
		}

		private void OnExitTransitionNameChanged(object sender, ItemNameChangedArgs args)
		{
			throw new NotImplementedException();
		}

		public virtual void OnActivated(StateActivatedReason reason)
		{
			glue.OnActivated(reason);
		}

		public virtual void OnDeactivated(StateDeactivatedReason reason)
		{
			glue.OnDeactivated(reason);
		}

		public GlueTrait Glue
		{
			get { return glue; }
		}

		class TransitionGlue : IStateTransitionGlue<GlueTrait>
		{
			StateDOM<GlueTrait> targetStateDOM;
			DynamicStateMachine<GlueTrait> stateMachine;

			DynamicState<GlueTrait> newState;

			public TransitionGlue(StateDOM<GlueTrait> targetStateDOM, 
				DynamicStateMachine<GlueTrait> stateMachine)
			{
				this.targetStateDOM = targetStateDOM;
				this.stateMachine = stateMachine;
			}

			public IState<GlueTrait> PrepareTransition()
			{
				newState = new DynamicState<GlueTrait>(targetStateDOM,stateMachine);
				return newState;
			}

			public void DoTransition()
			{
				stateMachine.GoToState(newState);
			}
		}
	}

	public class DynamicStateMachine<GlueTrait> : IStateMachine<GlueTrait>
		where GlueTrait : StateGlueTrait
	{
		StateMachineDOM<GlueTrait> dom;
		Stack<DynamicState<GlueTrait>> statesStack = new Stack<DynamicState<GlueTrait>>();
				
		IStateMachineUser<GlueTrait> user;

		public DynamicStateMachine(StateMachineDOM<GlueTrait> dom)
		{
			this.dom = dom;
		}

		public DynamicStateMachine(string name)
			: this(new StateMachineDOM<GlueTrait>(name))
		{
		}

		public DynamicState<GlueTrait> CurState
		{
			get 
			{
				Debug.Assert(statesStack.Count != 0, "There's no active state in this state machine");
				return statesStack.Peek();
			}
		}

		public void GoToState(DynamicState<GlueTrait> newState)
		{
			Debug.Assert(newState != null, "The state shouldn't be null");

			if (statesStack.Count != 0)
			{
				statesStack.Peek().OnDeactivated(
					StateDeactivatedReason.GoToOtherState);
				statesStack.Pop();
			}

			newState.Glue.SetStateMachine(this);
			user.InitializeGlue(newState.Glue);
			
			statesStack.Push(newState);
			newState.OnActivated(StateActivatedReason.GoToState);
		}

		public void PushState(DynamicState<GlueTrait> state)
		{
			Debug.Assert(state != null, "The state shouldn't be null");

			if (statesStack.Count != 0)
			{
				statesStack.Peek().OnDeactivated(
					StateDeactivatedReason.OtherStatePushed);
			}

			statesStack.Push(state);
			state.OnActivated(StateActivatedReason.Pushed);
		}

		public void PopState()
		{
			Debug.Assert(statesStack.Count != 0, "There's nothing to pop");
			Debug.Assert(statesStack.Count != 1, "You can't pop the last state in the states stack");

			statesStack.Pop().OnDeactivated(StateDeactivatedReason.Popped);
			statesStack.Peek().OnActivated(StateActivatedReason.OtherStatePopped);
		}

		/// <summary>
		/// Gets the document object model of this state machine. 
		/// </summary>
		public StateMachineDOM<GlueTrait> DOM
		{
			get { return dom; }
		}

		public void Start(IStateMachineUser<GlueTrait> user)
		{
			Debug.Assert(user != null, "user shouldn't be null");
			Debug.Assert(dom.InitialState != null, "No initial state set");

			this.user = user;

			DynamicState<GlueTrait> state = new DynamicState<GlueTrait>(dom.InitialState, this);
			GoToState(state);
		}

		public void Stop()
		{
			user = null;
		}

		public GlueTrait GetCurStateGlue()
		{
			Debug.Assert(user != null, "Can only be called when Start is called");
			return statesStack.Peek().Glue;
		}
	}
}